package com.trinity.web.controller.publicAPI;

import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.service.WxPayService;
import com.google.gson.Gson;
import com.trinity.common.annotation.Anonymous;
import com.trinity.common.exception.ServiceException;
import com.trinity.common.utils.DateUtils;
import com.trinity.order.refund.domain.XcRefundOrder;
import com.trinity.order.refund.service.IXcRefundOrderService;
import com.trinity.orders.constant.OrderConstants;
import com.trinity.orders.domain.XcOrders;
import com.trinity.orders.domain.XcPayRecord;
import com.trinity.orders.service.IXcOrdersService;
import com.trinity.orders.service.IXcPayRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Administrator
 */
@Slf4j
@RestController
@Anonymous
@RequestMapping("/public")
public class OrderNotifyApi {

    @Resource
    private WxPayService wxPayService;
    @Resource
    private IXcPayRecordService payRecordService;

    @Resource
    private IXcOrdersService ordersService;

    @Resource
    private IXcRefundOrderService refundOrderService;

    private final ReentrantLock lock = new ReentrantLock();

    private final ReentrantLock refundLock = new ReentrantLock();
    private Gson gson = new Gson();

    @PostMapping("/notify/order")
    public String parseOrderNotifyResult(@RequestBody String xmlData, HttpServletResponse response, HttpServletRequest request) {
        log.info("支付回调 ===> \n xmlData{}", xmlData);
        Map<String, String> map = new HashMap<>();
        try {
            //通知参数
            Map<String, Object> bodyMap = gson.fromJson(xmlData, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            log.info("支付通知的id：======> \n{}", requestId);

            //处理请求头
            String serialNo = request.getHeader("Wechatpay-Serial");
            String sign = request.getHeader("Wechatpay-Signature");
            String timestamp = request.getHeader("Wechatpay-TimeStamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            //校验参数
            SignatureHeader header = new SignatureHeader();
            header.setTimeStamp(timestamp);
            header.setNonce(nonce);
            header.setSerial(serialNo);
            header.setSignature(sign);
            // 签名验证 解密报文
            WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = wxPayService.parseOrderNotifyV3Result(xmlData, header);
            WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
            log.info("解析通知结果======>\n{}", result);
            processOrder(wxPayOrderNotifyV3Result);

            //响应成功！
            log.info("响应成功======>");
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        } catch (Exception e) {
            //失败响应
            log.info("失败响应======>");
            e.printStackTrace();
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }


    }

    /**
     * 处理支付订单
     *
     * @param notifyV3Result
     * @throws ParseException
     */
    private void processOrder(WxPayOrderNotifyV3Result notifyV3Result) throws ParseException {
        log.info("处理订单======>\n");
        //解密的报文
        WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyV3Result.getResult();
        String payNo = result.getOutTradeNo();
        //尝试获取锁: 成功返回 true 失败返回 false 需要主动释放锁
        if (lock.tryLock()) {
            try {
                String tradeState = result.getTradeState();
                XcPayRecord payRecord = payRecordService.selectPayRecordByPaNO(payNo);
                //处理重复通知
                //接口调用的幂等性
                XcOrders xcOrders = ordersService.selectXcOrdersById(payRecord.getOrderId());
                if (!OrderConstants.TradeState.NOTPAY.equals(xcOrders.getStatus())) {
                    return;
                }
                //更新订单状态
                updateOrder(payRecord.getOrderId(), tradeState);
                //记录支付记录
                updatePayRecord(result, payRecord);
                //更新支付记录
                payRecordService.updateById(payRecord);
                log.info("更新订单状态、支付记录======>\n");
            } finally {
                lock.unlock();
            }

        }


    }

    /**
     * 记录支付日志
     *
     * @param result    解密报文
     * @param payRecord 日志
     * @throws ParseException
     */
    private void updatePayRecord(WxPayOrderNotifyV3Result.DecryptNotifyResult result, XcPayRecord payRecord) throws ParseException {
        log.info("支付记录 =====>{}", payRecord.getPayNo());
        //appid
        payRecord.setAppid(result.getAppid());
        //mchid
        payRecord.setMchid(result.getMchid());
        //TransactionId
        payRecord.setOutPayNo(result.getTransactionId());
        payRecord.setTransactionId(result.getTransactionId());
        //tradeType
        payRecord.setPayMethods(result.getTradeType());
        payRecord.setTradeType(result.getTradeType());
        //tradeState
        payRecord.setStatus(result.getTradeState());
        payRecord.setTradeState(result.getTradeState());
        //tradeStateDesc
        payRecord.setTradeStateDesc(result.getTradeStateDesc());
        //bankType
        payRecord.setBankType(result.getBankType());
        //attach
        payRecord.setAttach(result.getAttach());
        //successTime 2024-02-28T10:45:24+08:00
        String timeEnd = result.getSuccessTime();
        SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        Date date = inputFormat.parse(timeEnd);
        payRecord.setPaySuccessTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date));
        payRecord.setSuccessTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date));
        // payer
        //Amount
        WxPayOrderNotifyV3Result.Amount amount = result.getAmount();
        payRecord.setTotalAmount(String.valueOf(amount.getPayerTotal()));
        //total
        payRecord.setTotal(String.valueOf(amount.getTotal()));
        //payerTotal
        payRecord.setPayerTotal(String.valueOf(amount.getPayerTotal()));
        //currency
        //payerCurrency
        payRecord.setPayerCurrency(amount.getPayerCurrency());
        //原始报文
        payRecord.setContent(gson.toJson(result));
    }

    /**
     * 更新订单状态
     *
     * @param orderId id
     * @param status  状态
     */
    private void updateOrder(String orderId, String status) {
        log.info("更新订单状态 =====>{}", orderId);
        //只有支付成功才更新订单状态
        if (OrderConstants.TradeState.SUCCESS.equals(status)){
            XcOrders xcOrders = new XcOrders();
            xcOrders.setId(orderId);
            xcOrders.setStatus(OrderConstants.TradeState.SUCCESS);
            ordersService.updateById(xcOrders);
        }

    }

    /**
     * 校验通知签名
     *
     * @param header 通知头信息
     * @param data   通知数据
     * @return true:校验通过 false:校验不通过
     */
    private boolean verifyNotifySign(SignatureHeader header, String data) {
        String beforeSign = String.format("%s\n%s\n%s\n",
                header.getTimeStamp(),
                header.getNonce(),
                data);
        return wxPayService.getConfig().getVerifier().verify(header.getSerial(),
                beforeSign.getBytes(StandardCharsets.UTF_8), header.getSignature());
    }

    /**
     * 下面详细描述对通知数据进行解密的流程：
     * <p>
     * 1、用商户平台上设置的APIv3密钥 【微信商户平台—>账户设置—>API安全—>设置APIv3密钥】 ，记为key；
     * 2、针对resource.algorithm中描述的算法（目前为AEAD_AES_256_GCM），取得对应的参数nonce和associated_data；
     * 3、使用key、nonce和associated_data，对数据密文resource.ciphertext进行解密，得到JSON形式的资源对象。
     * <p>
     * 注： AEAD_AES_256_GCM算法的接口细节，请参考rfc5116。微信支付使用的密钥key长度为32个字节，随机串nonce长度12个字节，associated_data长度小于16个字节并可能为空字符串。
     *
     * @param xmlData
     * @param response
     * @param request
     * @return
     */
    @RequestMapping(value = "/notify/refund", method = {RequestMethod.POST})
    public String parseRefundNotifyResult(@RequestBody String xmlData, HttpServletResponse response, HttpServletRequest request) {
        log.info("退款回调 ===> \n xmlData{}", xmlData);
        Map<String, String> map = new HashMap<>();
        try {
            //通知参数
            Map<String, Object> bodyMap = gson.fromJson(xmlData, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            log.info("退款回调：======> \n{}", requestId);

            //处理请求头
            String serialNo = request.getHeader("Wechatpay-Serial");
            String sign = request.getHeader("Wechatpay-Signature");
            String timestamp = request.getHeader("Wechatpay-TimeStamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            //校验参数
            SignatureHeader header = new SignatureHeader();
            header.setTimeStamp(timestamp);
            header.setNonce(nonce);
            header.setSerial(serialNo);
            header.setSignature(sign);
            String beforeSign = String.format("%s\n%s\n%s\n%s\n",
                    header.getTimeStamp(),
                    header.getNonce(),
                    header.getSerial(),
                    header.getSignature());
            log.info("\n退款通知：=====>请求头参数：\n{}",beforeSign);
            // 签名验证 解密报文
            WxPayRefundNotifyV3Result wxPayRefundNotifyV3Result = wxPayService.parseRefundNotifyV3Result(xmlData, header);
            WxPayRefundNotifyV3Result.DecryptNotifyResult result = wxPayRefundNotifyV3Result.getResult();
            log.info("解析通知结果======>\n{}", result);

            //处理订单 退款单 支付记录  订单
            processRefundOrder(wxPayRefundNotifyV3Result);

            //响应成功！
            log.info("响应成功======>");
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        } catch (Exception e) {
            //失败响应
            log.info("失败响应======>");
            e.printStackTrace();
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }
    }

    /**
     * 处理退款订单
     *
     * @param wxPayRefundNotifyV3Result
     */
    private void processRefundOrder(WxPayRefundNotifyV3Result wxPayRefundNotifyV3Result) {

        log.info("\n处理订单======>");
        //解密的报文
        WxPayRefundNotifyV3Result.DecryptNotifyResult result = wxPayRefundNotifyV3Result.getResult();
        //商户订单 orderId payNo
        String outTradeNo = result.getOutTradeNo();
        //退款订单
        String refundNo = result.getOutRefundNo();
        //微信支付退款单号
        String refundId = result.getRefundId();
        //微信支付订单号
        String transactionId = result.getTransactionId();
        //尝试获取锁: 成功返回 true 失败返回 false 需要主动释放锁
        if (refundLock.tryLock()) {
            // 核实 订单状态
            try {
                log.info("\n核实退款订单状态是否等待处理：========> {}", refundNo);
                XcRefundOrder refund = refundOrderService.selectRefundOrderByRefundNO(refundNo);
                if (refund == null) {
                    throw new ServiceException("退款的不存在");
                }
                if (!OrderConstants.RefundState.PROCESSING.equals(refund.getStatus())) {
                    //负载退款处理中的状态不处理
                    return;
                }
                //更新订单状态
                log.info("\n核实退款通知已退款：========> {}", refundNo);
                //订单已经退款
                refund.setRefundId(refundId);
                //状态
                refund.setStatus(result.getRefundStatus());
                //退款时间
                try {
                    String timeEnd = result.getSuccessTime();
                    SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
                    Date parse = inputFormat.parse(timeEnd);
                    refund.setSuccessTime(parse);
                } catch (ParseException e) {
                    Date nowDate = DateUtils.getNowDate();
                    refund.setSuccessTime(nowDate);
                }
                //退款金额
                refund.setRefund(result.getAmount().getRefund());
                refund.setTotal(String.valueOf(result.getAmount().getTotal()));

                log.info("\n更新退款订单状态、记录退款信息========> {}", refund);
                //更新退款单
                int i = refundOrderService.updateXcRefundOrder(refund);
                //成功更改订单状态
                if (result.getRefundStatus().equals(OrderConstants.TradeState.SUCCESS)) {
                    //更新支付记录
                    XcPayRecord payRecord = payRecordService.selectPayRecordByPaNO(outTradeNo);
                    payRecordService.updateXcPayRecordToRefundByTransactionId(transactionId, OrderConstants.TradeState.REFUND);

                    //更新订单
                    XcOrders xcOrders = ordersService.selectXcOrdersById(payRecord.getOrderId());
                    xcOrders.setStatus(OrderConstants.OrderState.FULLREFUND);
                    ordersService.updateXcOrders(xcOrders);
                }
            } finally {
                refundLock.unlock();
            }


        }
    }
}
